package org.cainiao.api.lark.impl.imperative;

import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.cainiao.api.lark.imperative.LarkApi;
import org.cainiao.api.lark.imperative.LarkApiWithAccessToken;
import org.cainiao.api.lark.imperative.LarkApiWithAppAccessToken;
import org.cainiao.api.lark.imperative.LarkApiWithOutAccessToken;
import org.cainiao.api.lark.imperative.authenticateandauthorize.AuthenticateAndAuthorize;
import org.cainiao.api.lark.imperative.authenticateandauthorize.AuthenticateAndAuthorizeWithAppAccessToken;
import org.cainiao.api.lark.imperative.authenticateandauthorize.AuthenticateAndAuthorizeWithOutAccessToken;
import org.cainiao.api.lark.imperative.authenticateandauthorize.getaccesstokens.restoperations.GetAccessTokensWithAppAccessToken;
import org.cainiao.api.lark.imperative.authenticateandauthorize.getaccesstokens.restoperations.GetAccessTokensWithOutAccessToken;
import org.cainiao.api.lark.imperative.docs.Docs;
import org.cainiao.api.lark.imperative.docs.board.thumbnail.Thumbnail;
import org.cainiao.api.lark.imperative.docs.docs.apireference.document.Document;
import org.cainiao.api.lark.imperative.docs.space.folder.Folder;
import org.cainiao.api.lark.impl.imperative.authenticateandauthorize.WebClientAuthenticateAndAuthorize;
import org.cainiao.api.lark.impl.imperative.authenticateandauthorize.WebClientAuthenticateAndAuthorizeWithAppAccessToken;
import org.cainiao.api.lark.impl.imperative.authenticateandauthorize.WebClientAuthenticateAndAuthorizeWithOutAccessToken;
import org.cainiao.api.lark.impl.imperative.authenticateandauthorize.getaccesstokens.WebClientGetAccessTokens;
import org.cainiao.api.lark.impl.imperative.authenticateandauthorize.getaccesstokens.WebClientGetAccessTokensWithAppAccessToken;
import org.cainiao.api.lark.impl.imperative.authenticateandauthorize.getaccesstokens.WebClientGetAccessTokensWithOutAccessToken;
import org.cainiao.api.lark.impl.imperative.docs.WebClientDocs;
import org.cainiao.api.lark.impl.imperative.docs.board.WebClientBoard;
import org.cainiao.api.lark.impl.imperative.docs.board.thumbnail.WebClientThumbnail;
import org.cainiao.api.lark.impl.imperative.docs.docs.WebClientDocsApi;
import org.cainiao.api.lark.impl.imperative.docs.docs.apireference.WebClientApiReference;
import org.cainiao.api.lark.impl.imperative.docs.docs.apireference.document.WebClientDocument;
import org.cainiao.api.lark.impl.imperative.docs.space.WebClientSpace;
import org.cainiao.api.lark.impl.imperative.docs.space.folder.WebClientFolder;
import org.cainiao.api.lark.impl.util.converter.LarkFieldNameMapResponseConverter;
import org.cainiao.api.lark.impl.util.converter.ResponseConverter;
import org.cainiao.api.lark.impl.util.provider.LarkAccessTokenProvider;
import org.cainiao.api.lark.impl.util.provider.LarkAppAccessTokenProvider;
import org.springframework.util.Assert;
import org.springframework.web.reactive.function.client.WebClient;

/**
 * 生产基于 WebClient 的各种 LarkApi 对象的工厂<br/>
 * 对 API 的拆分是为了避免循环依赖问题，用户可先用 LarkApi 对象，如发现循环依赖再在适当位置改用细粒度 API<br/>
 * 例如某些 API 需要 app access token，而获取 app access token 的组件是用户提供的对象，以便用户可按需缓存 token<br/>
 * 而用户维护 app access token 的组件中，在 token 过期重新获取时，可能又会用 LarkApi 对象来调用飞书接口<br/>
 * <p>
 * 注意避免在返回的拉姆达表达式中引用 WebClientLarkApiFactory 成员字段而导致 WebClientLarkApiFactory 对象无法被回收
 * <p>
 * Author: Cai Niao(wdhlzd@163.com)<br />
 */
@RequiredArgsConstructor
public class WebClientLarkApiFactory {

    public static final LarkAppAccessTokenProvider DEFAULT_APP_ACCESS_TOKEN_PROVIDER = () -> null;
    public static final LarkAccessTokenProvider DEFAULT_ACCESS_TOKEN_PROVIDER = () -> null;
    public static final ResponseConverter DEFAULT_RESPONSE_CONVERTER = new LarkFieldNameMapResponseConverter();

    private final WebClient webClient;
    private final String baseUrl;
    private final ResponseConverter responseConverter;

    private static LarkAppAccessTokenProvider getAppAccessTokenProvider(LarkAppAccessTokenProvider larkAppAccessTokenProvider) {
        return larkAppAccessTokenProvider == null ? DEFAULT_APP_ACCESS_TOKEN_PROVIDER : larkAppAccessTokenProvider;
    }

    private static LarkAccessTokenProvider getAccessTokenProvider(LarkAccessTokenProvider larkAccessTokenProvider) {
        return larkAccessTokenProvider == null ? DEFAULT_ACCESS_TOKEN_PROVIDER : larkAccessTokenProvider;
    }

    // ===

    public Document getDocument(LarkAccessTokenProvider larkAccessTokenProvider) {
        return new WebClientDocument(webClient, baseUrl,
            getAccessTokenProvider(larkAccessTokenProvider), responseConverter);
    }

    public GetAccessTokensWithAppAccessToken getGetAccessTokensWithAppAccessToken(
        LarkAppAccessTokenProvider larkAppAccessTokenProvider) {
        return new WebClientGetAccessTokensWithAppAccessToken(webClient, baseUrl,
            getAppAccessTokenProvider(larkAppAccessTokenProvider), responseConverter);
    }

    public Folder getFolder(LarkAccessTokenProvider larkAccessTokenProvider) {
        return new WebClientFolder(webClient, baseUrl,
            getAccessTokenProvider(larkAccessTokenProvider), responseConverter);
    }

    public Thumbnail getThumbnail(LarkAccessTokenProvider larkAccessTokenProvider) {
        return new WebClientThumbnail(webClient, baseUrl, getAccessTokenProvider(larkAccessTokenProvider));
    }

    public GetAccessTokensWithOutAccessToken getGetAccessTokensWithOutAccessToken() {
        return new WebClientGetAccessTokensWithOutAccessToken(webClient, baseUrl, responseConverter);
    }

    // ===

    public Docs getDocs(Folder folder, Document document, Thumbnail thumbnail) {
        return new WebClientDocs(
            new WebClientSpace(folder),
            new WebClientDocsApi(new WebClientApiReference(document)),
            new WebClientBoard(thumbnail));
    }

    public AuthenticateAndAuthorize getAuthenticateAndAuthorize(
        GetAccessTokensWithAppAccessToken getAccessTokensWithAppAccessToken,
        GetAccessTokensWithOutAccessToken getAccessTokensWithOutAccessToken) {

        return new WebClientAuthenticateAndAuthorize(new WebClientGetAccessTokens(
            getAccessTokensWithOutAccessToken, getAccessTokensWithAppAccessToken));
    }

    public LarkApi getLarkApi(AuthenticateAndAuthorize authenticateAndAuthorize, Docs docs) {
        return new WebClientLarkApi(authenticateAndAuthorize, docs);
    }

    public LarkApiWithAppAccessToken getLarkApiWithAppAccessToken(
        GetAccessTokensWithAppAccessToken getAccessTokensWithAppAccessToken) {

        return new WebClientLarkApiWithAppAccessToken(
            new WebClientAuthenticateAndAuthorizeWithAppAccessToken(getAccessTokensWithAppAccessToken));
    }

    public LarkApiWithAccessToken getLarkApiWithAccessToken(Docs docs) {
        return new WebClientLarkApiWithAccessToken(docs);
    }

    public LarkApiWithOutAccessToken getLarkApiWithOutAccessToken(
        GetAccessTokensWithOutAccessToken getAccessTokensWithOutAccessToken) {

        return new WebClientLarkApiWithOutAccessToken(
            new WebClientAuthenticateAndAuthorizeWithOutAccessToken(getAccessTokensWithOutAccessToken));
    }

    // === api Record start ===
    private record WebClientLarkApiWithAccessToken(Docs docs) implements LarkApiWithAccessToken {
    }

    private record WebClientLarkApi(AuthenticateAndAuthorize authenticateAndAuthorize, Docs docs) implements LarkApi {
    }

    private record WebClientLarkApiWithOutAccessToken(
        AuthenticateAndAuthorizeWithOutAccessToken authenticateAndAuthorizeWithOutAccessToken)
        implements LarkApiWithOutAccessToken {
    }

    private record WebClientLarkApiWithAppAccessToken(
        AuthenticateAndAuthorizeWithAppAccessToken authenticateAndAuthorizeWithAppAccessToken)
        implements LarkApiWithAppAccessToken {
    }
    // === api Record end ===

    // === api factory builder start ===
    public static WebClientLarkApiFactoryBuilder builder() {
        return new WebClientLarkApiFactoryBuilder();
    }

    @Data
    public static class WebClientLarkApiFactoryBuilder {
        private WebClient webClient;
        private String baseUrl;
        private ResponseConverter responseConverter = DEFAULT_RESPONSE_CONVERTER;

        private WebClientLarkApiFactoryBuilder() {
        }

        public WebClientLarkApiFactoryBuilder webClient(WebClient webClient) {
            this.webClient = webClient;
            return this;
        }

        public WebClientLarkApiFactoryBuilder baseUrl(String baseUrl) {
            this.baseUrl = baseUrl;
            return this;
        }

        public WebClientLarkApiFactoryBuilder responseConverter(ResponseConverter responseConverter) {
            this.responseConverter = responseConverter;
            return this;
        }

        public WebClientLarkApiFactory build() {
            Assert.notNull(webClient, "webClient cannot be null");
            Assert.notNull(baseUrl, "baseUrl cannot be null");
            return new WebClientLarkApiFactory(webClient, baseUrl, responseConverter);
        }
    }
    // === api factory builder end ===
}
